fix(extension): keep user-opened tabs free & fix agent-tab leak on session stop (issue #57) - #66
fix(extension): keep user-opened tabs free & fix agent-tab leak on session stop (issue #57)#66iAstro wants to merge 3 commits into
Conversation
|
Thanks @iAstro — the direction is right and the release-decision fix for #57 is clean, but there are 3 blockers before this can merge: 🔴 1. TypeScript doesn't compile: 🔴 2. Core functionality is dead code in production: the session_stop rework depends on 🔴 3. Conflicts with main: #52 changed 🟡 Also worth fixing: Once 1-3 are fixed this is valuable — happy to re-review! 方向没问题,修掉这三个卡点(尤其编译错误和 dispatcher 接线)后欢迎继续。 |
f2a9b0a to
f3529d4
Compare
…ssion stop (issue Tencent#57) - session_stop: exclude tabs still tracked in agentCreatedTabs from the release decision so a tab that failed to close is no longer mistaken for a user tab (which previously kept the window open and leaked the tab). - Distinguish user-created tabs from agent-created tabs in the Agent Window via a pending-tab counter consumed by chrome.tabs.onCreated, so tabs the user opens themselves (new-tab button / Cmd+T / open in new tab) are never taken over by the BrowserSkill control mask. - background overlay: decide per-tab state BEFORE showing controls (overlayStateForTab + pushOverlayStateForTab) so a user tab receives hidden on its first overlay.ready ping instead of flashing control then hiding. - AgentWindowApi.ensureActiveTab now resolves with the activated/created tab id instead of void, so SessionManager can record homeTabId. This fixes the tsc failure flagged in review (the interface previously returned Promise<void> while callers treated the result as number | null). - Wire the production tabManagement.tabs / tabsQuery deps into tool.session_stop, both in the dispatcher and in the disconnect cleanup. They were never injected, which left the agent-tab cleanup and the window-release path as dead code outside tests (behaviour identical to main). Added a dispatcher-level integration test that fails without the wiring: a surviving user tab must release the window, not close it.
f3529d4 to
a4fed13
Compare
…fails tool.tab_create increments pendingAgentTabCount *before* chrome.tabs.create so the onCreated listener can tell an agent tab from a user-opened tab. When the create failed, no onCreated event arrived and the counter leaked, so the next tab the user opened was misclassified as agent-created — the exact confusion the counter exists to prevent, a variant of issue Tencent#57. handleTabCreate now releases the slot on failure via SessionManager.releaseAgentTabPending. An abort (cancelled) is excluded: the tab did open (and was cleaned up), so its onCreated still consumes the slot. Added a regression test asserting that after a failed tab_create, a user-opened tab is still classified as 'user' (not 'agent').
…sue Tencent#57) SessionManager.classifyNewTab's `initializing` branch relied on a windowInitializing flag that start() cleared *before* the home tab's chrome.tabs.onCreated event was dispatched (the event is asynchronous), so the branch never ran in production and the home tab's classification was subject to a timing race. Match the home tab by its tab id instead — the id is already returned by ensureActiveTab and is reliable regardless of event ordering. Also refresh the overlay-bridge docstring: overlay.ready now makes the background *push* the per-tab overlay state (and the Agent Window also pushes on tab create / control-mode change), rather than a request/reply.
Addressed everything from the review: Blocker 1: ensureActiveTab now resolves with the tab id; homeTabId typechecks. |
Summary
Fixes issue #57 — in the Agent-controlled browser window, tabs the user opens
themselves should stay fully under the user's control (not taken over by
BrowserSkill), and session stop should never leak an agent-created tab.
Three concrete fixes:
session_stop—tool.session_stopdecided whether torelease (keep) the window by checking "are there any tabs left?". A tab that
failed to close was still present and got mistaken for a user tab → the
window was released and the agent tab leaked. Now the release decision
excludes any tab still tracked in
agentCreatedTabs, so a failed-to-closeagent tab forces the window to close instead.
userTabstracking plusa
pendingAgentTabCountcounter consumed bychrome.tabs.onCreatedto tellapart "agent-created via
tool.tab_create" from "user-created via Chrome UI".User tabs get a
hiddenoverlay and never show the control mask.overlayStateForTab/pushOverlayStateForTab, which decide per-tab state before sending it, so auser tab receives
hiddenon its very first ping.Post-review fixes (thanks @BB-fat)
AgentWindowApi.ensureActiveTabnow resolves with the tab id(
Promise<number>) instead ofPromise<void>, soSessionManagercan recordhomeTabIdandtsctypechecks.tabManagement.tabs/tabsQueryintotool.session_stop(dispatcher + disconnect cleanup). They were neverinjected, leaving the cleanup and window-release path dead outside tests.
Added a dispatcher-level integration test that fails without the wiring.
main(f06a514).handleTabCreatereleases the pending agent-tab slot whenchrome.tabs.createfails (an abort is excluded — the tab did open), so thecounter no longer leaks into the next user-opened tab.
windowInitializingbranch; the home tab ismatched by
homeTabId, reliable regardless ofonCreatedevent ordering.overlay.ready.Test plan
manager.test.ts:classifyNewTabreturnsagentfor the home tab (matchedby
homeTabId) and for a pendingtool.tab_create,userwhen nothing ispending; multiple pending agent tabs map to multiple
onCreatedevents; thepending slot is released when tab creation fails.
session.test.ts: regression — an agent tab that fails to close forces thewindow to close (not release), so it cannot leak.
dispatcher.test.ts: integration — a surviving user tab releases the windowinstead of closing it (fails without the dispatcher wiring).
tabs.test.ts: regression — after a failedtab_create, a user-opened tab isstill classified
user(notagent).pnpm lint(179 checks) andext:buildpass locally.free to operate (no control mask, no flash). On
session_stop, agent tabsclose and the window is released only when genuine user tabs remain.